package com.winning.pmph.interceptor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.winning.pmph.entity.OperationLog;
import com.winning.pmph.filter.F0SessionFilter;
import com.winning.pmph.service.OperationLogService;
import com.winning.pmph.utils.ApplicationContextUtil;
import com.winning.pmph.utils.PMPHAppUtil;
import com.winning.pmph.vo.ResultData;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Objects;

/**
 * @author admin
 * @RestControllerAdvice是@RestController注解的增强，可以实现三个方面的功能： 全局异常处理
 * 全局数据绑定
 * 全局数据预处理
 */
@Slf4j
@RestControllerAdvice
public class ResponseAdvice implements RequestBodyAdvice, ResponseBodyAdvice<Object> {

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private OperationLogService operationLogService;

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass,
                                  ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
        // 解决 同时启用统一标准格式封装功能ResponseAdvice和RestExceptionHandler,异常处理完后,
        // 会再次走beforeBodyWrite这个方法,methodParameter.getMethodAnnotation(ApiOperation.class);结果为空
        if (o instanceof ResultData) {
            return o;
        }
        Object resultObj = null;
        ApiOperation apiOperation = methodParameter.getMethodAnnotation(ApiOperation.class);
        // Controller直接返回String的话，SpringBoot是直接返回，需要手动转换成json。
        if (o instanceof String) {
            resultObj = objectMapper.writeValueAsString(ResultData.success(o));
        } else {
            resultObj = ResultData.success(o);
        }
        // 向日志表里插入日志,查询接口不添加日志
        if (methodParameter.getMethod().getName().contains("select")) {
            return resultObj;
        }
        Map<String, String> map = Maps.newHashMap();
        map.put("eventResult", "成功");
        map.put("url", httpServletRequest.getRequestURI());
        map.put("ip", httpServletRequest.getRemoteHost());
        JSONObject parameters = new JSONObject();
        parameters.put("requestBody", httpServletRequest.getAttribute("cacheRequestBody"));
        parameters.put("requestParam", httpServletRequest.getParameterMap());
        map.put("parameters", parameters.toJSONString());
        Api api = methodParameter.getDeclaringClass().getAnnotation(Api.class);
        if (Objects.nonNull(api)) {
            map.put("modules", api.value());
        }
        map.put("result", JSON.toJSONString(resultObj));
        insertOperationLog(apiOperation, map);
        boolean ajaxRequest = F0SessionFilter.isAjaxRequest(httpServletRequest);
        if (!ajaxRequest) {
            return o;
        }
        return resultObj;
    }

    /**
     * 默认全局异常处理
     *
     * @param e
     * @return
     * @ExceptionHandler 统一处理某一异常类, 减少代码重复率和复杂度
     * @ResponseStatus 指定客户端收到的http状态码
     */
    @ExceptionHandler(value = Exception.class)
    public ResultData<String> exceptionHandler(Exception e, ServletRequest request, HandlerMethod handlerMethod) {
        log.error("全局异常信息 ex={}", e.getMessage(), e);
        if (request != null && request instanceof ContentCachingRequestWrapper) {
            ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
            ApiOperation apiOperation = handlerMethod.getMethodAnnotation(ApiOperation.class);
            Map<String, String> map = Maps.newHashMap();
            map.put("eventResult", "失败");
            map.put("url", wrapper.getRequestURI());
            map.put("ip", getRemortIP((HttpServletRequest) request));
            map.put("parameters", StringUtils.toEncodedString(wrapper.getContentAsByteArray(), Charset.forName(wrapper.getCharacterEncoding())));
            map.put("modules", handlerMethod.getBean().getClass().getAnnotation(Api.class).value());
            map.put("result", JSON.toJSONString(ResultData.fail(e.getMessage())));
            // 向日志表里插入日志
            insertOperationLog(apiOperation, map);
        }
        return ResultData.fail(e.getMessage());
    }

    private void insertOperationLog(ApiOperation apiOperation, Map<String, String> map) {
        OperationLog operationLog = new OperationLog();
        operationLog.setUserName(PMPHAppUtil.getCurrentUserEnName());
        operationLog.setName(PMPHAppUtil.getCurrentUserCnName());
        if (Objects.nonNull(apiOperation)) {
            operationLog.setEventDescribe(apiOperation.notes());
        }
        operationLog.setEventResult(map.get("eventResult"));
        operationLog.setUrl(map.get("url"));
        operationLog.setIp(map.get("ip"));
        operationLog.setParameters(map.get("parameters"));
        operationLog.setModules(map.get("modules"));
        operationLog.setResult(map.get("result"));
        operationLogService.save(operationLog);
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        return httpInputMessage;
    }

    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        JSONObject cacheRequestBody = (JSONObject) ApplicationContextUtil.getRequest().getAttribute("cacheRequestBody");
        if (Objects.isNull(cacheRequestBody)) {
            cacheRequestBody = new JSONObject();
        }
        cacheRequestBody.put(methodParameter.getParameterName(), o);
        ApplicationContextUtil.getRequest().setAttribute("cacheRequestBody", cacheRequestBody);
//        ApplicationContextUtil.getRequest().getParameterMap().put(methodParameter.getParameterName(), new String[]{JSON.toJSONString(o)});
        return o;
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    private String getRemortIP(HttpServletRequest request) {
        if (request.getHeader("x-forwarded-for") == null) {
            return request.getRemoteAddr();
        }
        return request.getHeader("x-forwarded-for");
    }
}
